#include "rm15d.h"

#include <iostream>
#include <math.h>

#include <QRegExp>

#include <boost/asio.hpp>
using namespace boost::asio;

/**
\brief Gain multipliers.
*/
const double gainRanges[] = {
  1.0,
  10.0,
  100.0
};

/**
\brief Current multipliers.
 */
const double currentRanges[] = {
  0.1,
  1.0,
  10.0
};

/** \brief Size of the buffer into which data will be streamed into from the RM15-D. */
const int READ_BUFFER_SIZE = 1024;

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
RM15Sample::RM15Sample(RM15Device* device, int value, int gain, int current) :
Sample(device, -4095.0),
_value(value),
_gain(gain),
_current(current)
{
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
double RM15Sample::value() const {
  if (abs(_value) == abs(_nodata)) {
    return _nodata;
  }

  RM15Device* RM15 = static_cast<RM15Device*>(_device);

  switch (RM15->configuration()) {
    case RM15Device::WENNER:
    case RM15Device::TWIN_WIDE_REMOTE:
      return M_PI * resistance() * RM15->probeSeparation() * 2.0;
      break;

    case RM15Device::DOUBLE_DIPOLE:
      return M_PI * resistance() * RM15->probeSeparation() * 2.0 * 3.0;
      break;

    case RM15Device::TWIN_EQUAL_DISTANCE:
    default:
      return M_PI * resistance() * RM15->probeSeparation();
      break;
  }
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
std::string RM15Sample::raw() const {
  char buf[16];
  sprintf(buf, "%d\n%c%c\n", _value, _gain+'0', _current+'0');
  return std::string(buf);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
double RM15Sample::resistance() const {
  // Dummy values are recorded as 4095, and over-range values are -4095. We don't differentiate, but perhaps we ought to...
  if (abs(_value) == abs(_nodata)) {
    return _nodata;
  }

  return static_cast<double>(_value) * gainRanges[_gain] * currentRanges[_current];
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
int RM15Device::downloadData(std::vector<Sample*>& sampleVec, const size_t totalSamplesToRead) {
  // We don't need to do anything complicated, so we shall just use a single-threaded, synchronous read.
  io_service io_service;
  serial_port port(io_service, _port);

  if (!port.is_open()) {
    std::cerr << "Unable to open serial port \"" << _port << "\"" << std::endl;
    return 1;
  }

  port.set_option(serial_port_base::baud_rate(_baud));
  port.set_option(serial_port_base::character_size(8));
  port.set_option(serial_port_base::flow_control(serial_port_base::flow_control::none));
  port.set_option(serial_port_base::parity(serial_port_base::parity::none));
  port.set_option(serial_port_base::stop_bits(serial_port_base::stop_bits::one));

  std::cout << "Opening " << _port << " with settings: " << _baud << " " << "8N1" << std::endl;
  std::cout << "Waiting for RM15-D" << std::endl;

  size_t lineCount = 0;
  char readBuffer[READ_BUFFER_SIZE];
  QString sampleString;
  int bytesRead = -1;
  int i = 0;

  // Read all the data into a string before processing.
  while (lineCount < totalSamplesToRead * 2 && bytesRead != 0) {
    bytesRead = port.read_some(boost::asio::buffer(readBuffer, READ_BUFFER_SIZE));
    if (i == 0) {
      std::cout << "Downloading " << totalSamplesToRead << " samples" << std::endl;
    }
    size_t count = std::count(readBuffer, readBuffer + bytesRead, '\n');
    lineCount += count;
    sampleString += QString::fromAscii(readBuffer, bytesRead);

    if (i % 100 == 0) {
      std::cout << "." << std::flush;
    }

    ++i;
  }

  std::cout << std::endl;

  port.close();

  // Tokenize and store the raw data.
  QRegExp samplePattern("(\\d+)(?:\\r|\\n)+(\\d)(\\d)(?:\\r|\\n)+");

  int pos = 0;

  while ((pos = samplePattern.indexIn(sampleString, pos)) != -1) {
    RM15Sample* s = new RM15Sample(this, samplePattern.cap(1).toInt(), samplePattern.cap(2).toInt(), samplePattern.cap(3).toInt());
    sampleVec.push_back(s);
    pos += samplePattern.matchedLength();
  }
  
  return 0;
}
